using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;

namespace etcetera.Mvc.UI.Html
{
    public class OptionGroupSelectList<T> : IEnumerable<SelectListItem>
    {
        private readonly Func<T, string> _optionGroupPropertyValueFunc;
        private IEnumerable<T> _items;

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionGroupSelectList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <param name="dataValueField">The data value field.</param>
        /// <param name="dataTextField">The data text field.</param>
        /// <param name="optionGroupSpecifier">The option group specifier.</param>
        public OptionGroupSelectList(IEnumerable<T> items, string dataValueField, string dataTextField, Func<T, string> optionGroupSpecifier)
        {
            _optionGroupPropertyValueFunc = optionGroupSpecifier;
            Init(items, dataValueField, dataTextField);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionGroupSelectList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <param name="dataValuePropertySpecifier">The data value property specifier.</param>
        /// <param name="dataTextPropertySpecifier">The data text property specifier.</param>
        /// <param name="optionGroupSpecifier">The option group specifier.</param>
        public OptionGroupSelectList(IEnumerable<T> items, 
                                     Expression<Func<T, object>> dataValuePropertySpecifier,
                                     Expression<Func<T, object>> dataTextPropertySpecifier, 
                                     Func<T, string> optionGroupSpecifier)
        {
            _optionGroupPropertyValueFunc = optionGroupSpecifier;
            
            var memberExpression = GetMemberExpression(dataValuePropertySpecifier);
            string dataValueField = memberExpression == null ? null : memberExpression.Member.Name;
            memberExpression = GetMemberExpression(dataTextPropertySpecifier);
            string dataTextField = memberExpression == null ? null : memberExpression.Member.Name;
            
            Init(items, dataValueField, dataTextField);
        }

        internal SelectList SelectList { get; private set; }
        internal IEnumerable<T> Items { get { return _items; } }
        internal Func<T, string> OptionGroupPropertyValueFunc { get { return _optionGroupPropertyValueFunc; } }

        private void Init(IEnumerable<T> items, string dataValueField, string dataTextField)
        {
            SelectList = new SelectList(items, dataValueField, dataTextField);
            _items = items;
        }

        private static MemberExpression GetMemberExpression(LambdaExpression expression)
        {
            return RemoveUnary(expression.Body) as MemberExpression;
        }

        private static Expression RemoveUnary(Expression body)
        {
            var unary = body as UnaryExpression;
            if (unary != null)
            {
                return unary.Operand;
            }
            return body;
        }
        
        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<SelectListItem> GetEnumerator()
        {
            return SelectList.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}